iT邦幫忙

2023 iThome 鐵人賽

DAY 4
0

今天這篇跟 Laravel 沒有什麼關係,各個程式語言都有 閉包 (closure) 的使用,我自己是在看 Laravel 文件時才第一次看到,對當時的我來說檢視是每個英文字都懂,實際在幹嘛完全不知道…..因此今天先來講講 閉包(closure) 。

本篇文章使用強者我同學正正的筆記,與 GhatGpt 協助文字描述,並增加一些自己的圖解。

匿名函示 / 閉包(closure)簡介

閉包(Closure)是一種在程式設計中常見的概念,特別是在函數式程式設計和某些程式語言中,如JavaScript、Python、Swift等,它具有特殊的含義和用途。

閉包是一個函數,它包含了它被建立時所處的作用域(或者說環境)中的變數的引用即使在建立它的作用域外部呼叫這個函數時,它仍然能夠訪問這些變數。這意味著閉包可以捕獲並保留外部作用域中的狀態信息,使得函數在不同的上下文中執行時能夠保持一致的行為。

通常,閉包由以下幾個要素組成:

  1. 函數:閉包是一個函數,可以包含一些程式碼塊。
  2. 外部作用域:閉包可以訪問其被建立時所在的外部作用域中的變數。
  3. 內部狀態:閉包會捕獲並保留外部作用域中的變數的引用,以便在以後的呼叫中使用。

閉包的主要用途包括:

  1. 資料封裝和隱藏:通過將資料和操作封裝在一個閉包中,可以創建私有變數和方法,從而實現信息隱藏和封裝性。
  2. 函數工廠:閉包可以用於動態生成函數,每次生成的函數可以訪問不同的外部狀態。
  3. 回調函數:在異步程式設計中,閉包常常用作回調函數,用於在異步操作完成後執行特定的操作。
  4. 保持狀態:閉包可以用來保持狀態,例如在迭代過程中,每次呼叫都可以更新狀態並記住上一次的狀態。

在不同的程式語言中,閉包的具體語法和用法可能有所不同,但基本概念是通用的。閉包是一種強大的程式設計工具,可以使程式碼更加靈活和可複用。


範例說明

基本範例

// 這是一個接受三個參數的函數。前兩個參數 $a 和 $b 是要進行操作的數字,而第三個參數 $operation 是一個函數,用來執行特定的計算操作。
function calculate($a, $b, $operation) {
    return $operation($a, $b);
}

// 定義 $addition 匿名函數
		// 這裡定義了一個匿名函數(無名函數),這個匿名函數接受兩個參數 $a 和 $b,並返回它們的和再加上 5。這個匿名函數實現了一個簡單的加法操作,但多了一個固定的加數 5。
$addition = function($a, $b) {
    return $a + $b + 5;
};

// 使用 calculate 函數執行計算:
		// 在這裡,我們使用 calculate 函數,將數字 10 和 7 以及 $addition 匿名函數作為參數傳遞給它。$addition 函數被傳遞給 calculate 函數,並在 calculate 函數內部被稱為 $operation。
$result = calculate(10, 7, $addition);

上面舉例可以寫成更簡便的閉包

function calculate($a, $b, $operation) {
    return $operation($a, $b);
}

// 更簡化一點
$result = calculate(10, 7, function($a, $b) {
    return $a + $b + 5;
});

echo $result; // 22

在這個版本中,我們將匿名函數直接傳遞給 calculate 函數,而不需要事先定義 $addition 函數。這樣可以減少不必要的中間步驟,使程式碼更加簡潔和易讀。這個匿名函數在 calculate 函數內部被稱為 $operation,然後執行相同的加法操作,返回結果為 22。

https://ithelp.ithome.com.tw/upload/images/20230918/201628935vL4iGLlvv.png

我們可以將程式碼分為兩個作用域:外部作用域(Outer Scope)和內部作用域(Inner Scope)。外部作用域是閉包被建立的地方,而內部作用域是閉包內部的函數體。

當你需要對一個現有的功能進行擴展,但不能修改原來的程式碼需要保持它的原始功能,這時你可以使用閉包的方法。閉包可以讓你新增額外的功能,而不影響原本的功能。這樣你就可以在不改變原本的功能的情況下,為它添加新的功能或自定義行為。

進階一點點

當需要對一組數據進行特定操作,但這個操作可能會在不同地方多次使用,閉包(匿名函數)就可以派上用場。

// 定義一個閉包,用於計算數字的平方
$square = function($number) {
    return $number * $number;
};

// 定義通用的資料運算函數,接受閉包作為參數
function calculate($operation, $data) {
    $result = [];

    foreach ($data as $number) {
        $result[] = $operation($number);
    }

    return $result;
}

// 假設我們有一組數據
$data = [1, 2, 3, 4, 5];

// 使用 calculate 函數,傳入閉包 $square 和數據
$numbersSquared = calculate($square, $data);

// 輸出運算結果
print_r($numbersSquared);

這個例子中,我們首先定義了 $square 閉包,然後,我們定義了 calculate 函數,它接受一個資料陣列和一個閉包函數作為參數,然後對資料陣列應用閉包操作。這樣的設計讓程式碼更容易理解,因為函數的用途和功能更清晰。

https://ithelp.ithome.com.tw/upload/images/20230918/201628931M4NhVRLuj.png


function() use() 用法

閉包除了在函式中塞入另一個函式外,也可以取得外部變數,一起放進肚子裡進行處理。

$outerVar = 10; // 外部作用域的變數

// 創建一個閉包,使用 use 捕獲外部變數 $outerVar
$myClosure = function ($innerVar) use ($outerVar) {
    return $innerVar + $outerVar;
};

// 呼叫閉包,傳入內部變數 $innerVar
$result = $myClosure(5);

echo $result; // 輸出 15,因為 $innerVar(5) + $outerVar(10) = 15

https://ithelp.ithome.com.tw/upload/images/20230918/201628935PfxqfnnGo.png

在這個例子中:

  • 我們創建了一個匿名函數 $myClosure,它接受一個參數 $innerVar,同時使用 use 捕獲了外部變數 $outerVar
  • 然後,我們呼叫了這個閉包並傳入了 $innerVar 的值為 5
  • 閉包內部的計算是 $innerVar + $outerVar,所以最終結果是 5 + 10 = 15

這個例子演示了如何在閉包中使用 use 來捕獲外部變數,以便在閉包中使用它們。這種技巧在許多情況下非常有用,例如當你需要在閉包中訪問外部變數時。

不過這種情況並不是必須的,只是一種寫法。使用use只是把外部變數放進來比較好閱讀的一種方式而已,並不代表會有一種狀況一定需要使用use,我也可以把所有需要參數放進function裏面。


其他專案碰過的範例

關鍵字搜尋功能中的 時間搜尋

// 時間搜尋
$posts->when($timeStart || $timeEnd, function ($repairs) use ($timeStart, $timeEnd) {
    // 使用 "when" 方法檢查 $timeStart 或 $timeEnd 是否存在
    $posts->when($timeStart, function ($repairs) use ($timeStart) {
        // 如果 $timeStart 存在,則執行這個閉包
        // 在查詢中添加條件,要求 "created_at" 大於或等於 $timeStart
        $posts->where('created_at', '>=', $timeStart);
    });
    
    $posts->when($timeEnd, function ($repairs) use ($timeEnd) {
        // 如果 $timeEnd 存在,則執行這個閉包
        // 在查詢中添加條件,要求 "created_at" 小於或等於 $timeEnd
        $posts->where('created_at', '<=', $timeEnd);
    });
});

https://ithelp.ithome.com.tw/upload/images/20230918/20162893fwbGWeq4NQ.png

這個例子用了三個 閉包,並引入 $timeStart 及 $timeEnd 等外部變數

  1. 第一個閉包是當 $timeStart$timeEnd 存在時,要進行後續判斷
  2. 第二個閉包被包在第一個閉包裡面, 當**$timeStart** 存在時,則向查詢中添加一個條件,要求 created_at 大於或等於 $timeStart
  3. 第三個閉包也被包在第一個閉包裡面, 當**$timeEnd** 存在時,則向查詢中添加一個條件,要求 created_at 大於或等於 $timeEnd

關鍵字搜尋功能中的 模糊搜尋

$posts->when($keyword, function ($posts) use ($keyword) {
    // 使用 "when" 方法檢查 $keyword 是否存在
    $posts->where(function ($posts) use ($keyword) {
        // 在這個閉包中建立一個關鍵字的模糊搜尋條件
        $posts
            ->orWhere('post_number', 'like', "%$keyword%") // 文章編號
            ->orWhere('post_title', 'like', "%$keyword%") // 文章標題
            ->orWhere('post_content', 'like', "%$keyword%") // 文章內容
            ->orWhere('author_name', 'like', "%$keyword%") // 作者名稱
            ->orWhereHas('comments', function ($commentQuery) use ($keyword) {
                // 使用 "orWhereHas" 檢查關聯 "comments" 的內容
                $commentQuery
                    ->where('content', 'like', "%$keyword%");
            })
            ->orWhereHas('tags', function ($tagQuery) use ($keyword) {
                // 使用 "orWhereHas" 檢查關聯 "tags" 的標籤名稱
                $tagQuery
                    ->where('name', 'like', "%$keyword%");
            });
    });
});

https://ithelp.ithome.com.tw/upload/images/20230918/20162893xiM9U1jH1Y.png

這裡用了四個閉包,四個閉包都使用外部引進的同一個 $keyword變數,可以用顏色判斷一下他們彼此間的關係。

程式碼說明:

  1. when 方法:這是 Laravel 提供的一個條件式方法,用於根據給定的條件執行閉包內的操作。在這個例子中,如果 $keyword 存在(不為空或不為 null),則執行閉包內的操作。
  2. 外層閉包:這是外層的匿名函數,它接受一個參數 $posts,這是一個資料庫查詢建構器(Eloquent 查詢)。外層閉包主要負責根據條件添加搜尋條件。
  3. 內層閉包:在外層閉包中使用 where 方法建立了一個複雜的搜尋條件。這個內層閉包接受一個參數 $posts,這是相同的資料庫查詢建構器,用於添加搜尋條件。
  4. orWhere 方法:這是用於在查詢中添加 OR 條件的方法。在這個例子中,我們使用 orWhere 方法多次,分別對不同的欄位進行模糊搜尋。
  5. orWhereHas 方法:這是用於檢查關聯模型的方法。我們在這個例子中使用 orWhereHas 來檢查關聯的 commentstags,並在這些關聯上進行額外的搜尋。

這段程式碼的閉包使用讓你能夠根據關鍵字的存在與否,動態地建立複雜的搜尋條件。如果關鍵字存在,則會根據不同的欄位進行模糊搜尋,同時也會檢查關聯模型上的內容。這種方式讓你可以靈活地執行不同類型的搜尋操作。


上一篇
資料庫規劃:Database設計、ER Diagram
下一篇
Laravel Request Lifecycle 請求的生命週期
系列文
Laravel 後端菜鳥可以知道的流程概念30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言